由于在X86平台上,内存是划分为低端内存和高端内存的,所以在这两个区域内的page查找对应的虚拟地址是不一样的。
page_address()的定义
在include/linux/mm.h
中,有对page_address()
函数的三种定义,主要依赖于不同的平台:
首先看几个宏的定义:
所以下面page_address()的定义在i386上是没有定义的:
1 2 3 4 5 6 7 8
| #if defined(WANT_PAGE_VIRTUAL) #define page_address(page) ((page)->virtual) #define set_page_address(page, address) \\ do { \\ (page)->virtual = (address); \\ } while(0) #define page_address_init() do { } while(0) #endif
|
在没有配置CONFIG_HIGHMEM的i386平台上,page_address是这样定义的:
1 2 3 4 5
| #if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL) #define page_address(page) lowmem_page_address(page) #define set_page_address(page, address) do { } while(0) #define page_address_init() do { } while(0) #endif
|
而在支持高端内存的i386平台上,page_address()定义如下:
1 2 3 4 5
| #if defined(HASHED_PAGE_VIRTUAL) void *page_address(struct page *page); void set_page_address(struct page *page, void *virtual); void page_address_init(void); #endif
|
page_address()在低端内存中的实现
函数实现如下:
1 2 3 4 5 6 7
| #define page_address(page) lowmem_page_address(page) static __always_inline void *lowmem_page_address(const struct page *page) { return page_to_virt(page); } #define page_to_virt(x) __va(PFN_PHYS(page_to_pfn(x))) #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
|
小于896M的物理地址空间和3G~3G+896M的内核地址空间是一一对应映射的,所以只要知道page对应的物理地址就可以知道这个page对应的线性地址空间(pa + PAGE_OFFSET)。在低端内存中,通过页page(struct page* page)取得虚拟地址就是这样转换的。
page_address()在高端内存中的实现
在有配置CONFIG_HIGHMEM的i386平台上,page_address的实现在mm/highmem.c中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void *page_address(const struct page *page) { unsigned long flags; void *ret; struct page_address_slot *pas; if (!PageHighMem(page)) return lowmem_page_address(page); pas = page_slot(page); ret = NULL; spin_lock_irqsave(&pas->lock, flags); if (!list_empty(&pas->lh)) { struct page_address_map *pam; list_for_each_entry(pam, &pas->lh, list) { if (pam->page == page) { ret = pam->virtual; goto done; } } } done: spin_unlock_irqrestore(&pas->lock, flags); return ret; }
|
在高端内存中,由于不能通过像在低端内存中一样,直接通过物理地址加PAGE_OFFSET得到线性地址,所以引入了一个叫做page_address_map的结构,该结构保存每个page(仅高端内存中的)和对应的虚拟地址,所有高端内存中的这种映射通过链表链接起来,这个结构是在高端内存映射的时候建立,并加入到链表中的。
1 2 3 4 5
| struct page_address_map { struct page *page; void *virtual; struct list_head list; };
|
又因为如果内存远远大于896M,那么高端内存中的page就比较多,如果只用一个链表来表示,那么查找起来就比较耗时,所以这里引入了HASH算法,采用多个链表,每个page通过一定的HASH算法,对应到一个链表上,总共有128个链表:
1 2 3 4
| static struct page_address_slot { struct list_head lh; spinlock_t lock; } ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
|
PA_HASH_ORDER=7,所有一共有1<<7(128)个链表,每个page通过HASH算法后对应一个page_address_htable链表,然后再遍历这个链表来找到对应的PAGE和虚拟地址。
说明
本文转自page_address()函数分析。